/*-------------------------------------------------------------------------
 *
 * prepagg.c
 *	  预处理聚合函数调用的例程
 *
 * 如果查询中有相同的聚合调用，只需计算一次。此外，一些聚合函数可以共享相同的过渡状态，因此我们只需
 * 单独调用最终函数。这些优化与聚合的执行方式无关。
 *
 * preprocess_aggrefs() 检测这些情况，为每个需要计算的聚合和过渡状态创建 AggInfo 和 AggTransInfo 结构，并相应地设置
 * Aggrefs 中的 'aggno' 和 'transno' 字段。它还解析多态过渡类型，并相应地设置 'aggtranstype' 字段。
 *
 * XXX: AggInfo 和 AggTransInfo 结构在计划后被丢弃，因此执行器启动必须执行我们在这里做的一些过渡函数和初始值的查找。
 * 有一天，我们可能想把这些信息带到 Agg 节点上，以节省在执行器启动时的努力。然而，Agg 节点在规划中构建得较晚，因此
 * 这并不简单。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/optimizer/prep/prepagg.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/htup_details.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_type.h"
#include "nodes/nodeFuncs.h"
#include "nodes/pathnodes.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/optimizer.h"
#include "optimizer/plancat.h"
#include "optimizer/prep.h"
#include "parser/parse_agg.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/syscache.h"

static bool fc_preprocess_aggrefs_walker(Node *fc_node, PlannerInfo *fc_root);
static int	fc_find_compatible_agg(PlannerInfo *fc_root, Aggref *fc_newagg,
								List **fc_same_input_transnos);
static int	fc_find_compatible_trans(PlannerInfo *fc_root, Aggref *fc_newagg,
								  bool fc_shareable,
								  Oid fc_aggtransfn, Oid fc_aggtranstype,
								  int fc_transtypeLen, bool fc_transtypeByVal,
								  Oid fc_aggcombinefn,
								  Oid fc_aggserialfn, Oid fc_aggdeserialfn,
								  Datum fc_initValue, bool fc_initValueIsNull,
								  List *fc_transnos);
static Datum fc_GetAggInitVal(Datum fc_textInitVal, Oid fc_transtype);

/* -----------------
 * 解析所有Aggrefs的转换类型，并确定哪些Aggrefs可以共享聚合或转换状态。
 *
 * 有关聚合和转换函数的信息收集在root->agginfos和root->aggtransinfos列表中。每个Aggref的'aggtranstype'，
 * 'aggno'和'aggtransno'字段会被填充。
 *
 * 注意：这会就地修改输入表达式中的Aggrefs！
 *
 * 我们尝试通过检测重复的聚合函数来进行优化，以便其状态和最终值可以重复使用，而不必单独重新计算。我们还会检测那些不相同但可以共享相同转换状态的聚合。
 *
 * 场景：
 *
 * 1. 查询中出现相同的聚合函数调用：
 *
 *	  SELECT SUM(x) FROM ... HAVING SUM(x) > 0
 *
 *	  由于这些聚合是相同的，我们只需计算一次值。两个聚合将共享相同的'aggno'值。
 *
 * 2. 查询中出现两个不同的聚合函数，但聚合具有相同的参数、转换函数和初始值（并且，可能有不同的最终函数）：
 *
 *	  SELECT AVG(x), STDDEV(x) FROM ...
 *
 *	  在这种情况下，我们必须为不同的聚合创建一个新的AggInfo，并且我们需要分别调用最终函数，但我们只需运行一次转换函数。（这要求最终函数不会破坏转换状态，但出于其他原因这是必要的。）
 *
 * 为了使这两种优化有效，过渡阶段中使用的所有聚合属性必须相同，包括任何修饰符，例如ORDER BY、DISTINCT和FILTER，并且参数不能包含任何易变函数。
 * -----------------
 */
void preprocess_aggrefs(PlannerInfo *fc_root, Node *fc_clause)
{
	(void) fc_preprocess_aggrefs_walker(fc_clause, fc_root);
}

static void fc_preprocess_aggref(Aggref *fc_aggref, PlannerInfo *fc_root)
{
	HeapTuple	fc_aggTuple;
	Form_pg_aggregate fc_aggform;
	Oid			fc_aggtransfn;
	Oid			fc_aggfinalfn;
	Oid			fc_aggcombinefn;
	Oid			fc_aggserialfn;
	Oid			fc_aggdeserialfn;
	Oid			fc_aggtranstype;
	int32		fc_aggtranstypmod;
	int32		fc_aggtransspace;
	bool		fc_shareable;
	int			fc_aggno;
	int			fc_transno;
	List	   *fc_same_input_transnos;
	int16		fc_resulttypeLen;
	bool		fc_resulttypeByVal;
	Datum		fc_textInitVal;
	Datum		fc_initValue;
	bool		fc_initValueIsNull;
	bool		fc_transtypeByVal;
	int16		fc_transtypeLen;
	Oid			fc_inputTypes[FUNC_MAX_ARGS];
	int			fc_numArguments;

	Assert(fc_aggref->agglevelsup == 0);

	/*
	 * 从pg_aggregate获取有关聚合的信息。请注意，忽略移动聚合变体是正确的，因为我们在这里关注的是聚合而不是窗口函数。
	 */
	fc_aggTuple = SearchSysCache1(AGGFNOID,
							   ObjectIdGetDatum(fc_aggref->aggfnoid));
	if (!HeapTupleIsValid(fc_aggTuple))
		elog(ERROR, "cache lookup failed for aggregate %u",
			 fc_aggref->aggfnoid);
	fc_aggform = (Form_pg_aggregate) GETSTRUCT(fc_aggTuple);
	fc_aggtransfn = fc_aggform->aggtransfn;
	fc_aggfinalfn = fc_aggform->aggfinalfn;
	fc_aggcombinefn = fc_aggform->aggcombinefn;
	fc_aggserialfn = fc_aggform->aggserialfn;
	fc_aggdeserialfn = fc_aggform->aggdeserialfn;
	fc_aggtranstype = fc_aggform->aggtranstype;
	fc_aggtransspace = fc_aggform->aggtransspace;

	/*
	 * 解决可能的多态聚合转换类型。
	 */

	/* 提取参数类型（忽略任何ORDER BY表达式） */
	fc_numArguments = get_aggregate_argtypes(fc_aggref, fc_inputTypes);

	/* 解析过渡状态的实际类型，如果是多态的 */
	fc_aggtranstype = resolve_aggregate_transtype(fc_aggref->aggfnoid,
											   fc_aggtranstype,
											   fc_inputTypes,
											   fc_numArguments);
	fc_aggref->aggtranstype = fc_aggtranstype;

	/*
	 * 如果转换状态与第一个聚合输入的类型相同，假设它的typmod（宽度相同）也是如此。这适用于MAX/MIN之类的情况，可能在其他情况下也是合理的。
	 */
	fc_aggtranstypmod = -1;
	if (fc_aggref->args)
	{
		TargetEntry *fc_tle = (TargetEntry *) linitial(fc_aggref->args);

		if (fc_aggtranstype == exprType((Node *) fc_tle->expr))
			fc_aggtranstypmod = exprTypmod((Node *) fc_tle->expr);
	}

	/*
	 * 如果finalfn被标记为读写，我们无法共享转换状态；但对于AGGMODIFY_SHAREABLE聚合，可以共享状态。
	 *
	 * 原则上，在部分聚合中，即使最终函数被标记为读写，我们也可以共享转义状态，因为部分聚合不执行最终函数。但现在还为时已晚，无法知道我们是否会执行部分聚合。
	 */
	fc_shareable = (fc_aggform->aggfinalmodify != AGGMODIFY_READ_WRITE);

	/* 获取输出值数据类型的信息 */
	get_typlenbyval(fc_aggref->aggtype,
					&fc_resulttypeLen,
					&fc_resulttypeByVal);

	/* 获取初始值 */
	fc_textInitVal = SysCacheGetAttr(AGGFNOID, fc_aggTuple,
								  Anum_pg_aggregate_agginitval,
								  &fc_initValueIsNull);
	if (fc_initValueIsNull)
		fc_initValue = (Datum) 0;
	else
		fc_initValue = fc_GetAggInitVal(fc_textInitVal, fc_aggtranstype);

	ReleaseSysCache(fc_aggTuple);

	/*
	 * 1. 查看这是否与我们已经见过的另一个聚合函数调用相同。
	 */
	fc_aggno = fc_find_compatible_agg(fc_root, fc_aggref, &fc_same_input_transnos);
	if (fc_aggno != -1)
	{
		AggInfo    *fc_agginfo = list_nth(fc_root->agginfos, fc_aggno);

		fc_transno = fc_agginfo->transno;
	}
	else
	{
		AggInfo    *fc_agginfo = palloc(sizeof(AggInfo));

		fc_agginfo->finalfn_oid = fc_aggfinalfn;
		fc_agginfo->representative_aggref = fc_aggref;
		fc_agginfo->shareable = fc_shareable;

		fc_aggno = list_length(fc_root->agginfos);
		fc_root->agginfos = lappend(fc_root->agginfos, fc_agginfo);

		/*
		 * 统计它，并检查需要有序输入的情况。请注意，有序集聚合始终具有非空aggorder。任何有序输入情况也会阻碍部分聚合。
		 */
		if (fc_aggref->aggorder != NIL || fc_aggref->aggdistinct != NIL)
		{
			fc_root->numOrderedAggs++;
			fc_root->hasNonPartialAggs = true;
		}

		get_typlenbyval(fc_aggtranstype,
						&fc_transtypeLen,
						&fc_transtypeByVal);

		/*
		 * 2. 查看这个聚合是否可以与我们已经初始化的另一个聚合共享转换状态。
		 */
		fc_transno = fc_find_compatible_trans(fc_root, fc_aggref, fc_shareable,
										fc_aggtransfn, fc_aggtranstype,
										fc_transtypeLen, fc_transtypeByVal,
										fc_aggcombinefn,
										fc_aggserialfn, fc_aggdeserialfn,
										fc_initValue, fc_initValueIsNull,
										fc_same_input_transnos);
		if (fc_transno == -1)
		{
			AggTransInfo *fc_transinfo = palloc(sizeof(AggTransInfo));

			fc_transinfo->args = fc_aggref->args;
			fc_transinfo->aggfilter = fc_aggref->aggfilter;
			fc_transinfo->transfn_oid = fc_aggtransfn;
			fc_transinfo->combinefn_oid = fc_aggcombinefn;
			fc_transinfo->serialfn_oid = fc_aggserialfn;
			fc_transinfo->deserialfn_oid = fc_aggdeserialfn;
			fc_transinfo->aggtranstype = fc_aggtranstype;
			fc_transinfo->aggtranstypmod = fc_aggtranstypmod;
			fc_transinfo->transtypeLen = fc_transtypeLen;
			fc_transinfo->transtypeByVal = fc_transtypeByVal;
			fc_transinfo->aggtransspace = fc_aggtransspace;
			fc_transinfo->initValue = fc_initValue;
			fc_transinfo->initValueIsNull = fc_initValueIsNull;

			fc_transno = list_length(fc_root->aggtransinfos);
			fc_root->aggtransinfos = lappend(fc_root->aggtransinfos, fc_transinfo);

			/*
			 * 检查部分聚合是否可行，除非我们已经发现无法执行它。
			 */
			if (!fc_root->hasNonPartialAggs)
			{
				/*
				 * 如果没有合并函数，那么部分聚合是不可能的。
				 */
				if (!OidIsValid(fc_transinfo->combinefn_oid))
					fc_root->hasNonPartialAggs = true;

				/*
				 * 如果我们有任何transtype为INTERNAL的聚合，则必须检查它们是否具有序列化/反序列化函数；如果没有，我们无法序列化部分聚合结果。
				 */
				else if (fc_transinfo->aggtranstype == INTERNALOID &&
						 (!OidIsValid(fc_transinfo->serialfn_oid) ||
						  !OidIsValid(fc_transinfo->deserialfn_oid)))
					fc_root->hasNonSerialAggs = true;
			}
		}
		fc_agginfo->transno = fc_transno;
	}

	/*
	 * 填充Aggref中的字段（aggtranstype已经在上面设置）
	 */
	fc_aggref->aggno = fc_aggno;
	fc_aggref->aggtransno = fc_transno;
}

static bool fc_preprocess_aggrefs_walker(Node *fc_node, PlannerInfo *fc_root)
{
	if (fc_node == NULL)
		return false;
	if (IsA(fc_node, Aggref))
	{
		Aggref	   *fc_aggref = (Aggref *) fc_node;

		fc_preprocess_aggref(fc_aggref, fc_root);

		/*
		 * 我们假设解析器检查过聚合参数、直接参数或过滤子句中没有聚合（无论如何在此级别）。因此，我们无需递归到它们中的任何一个。
		 */
		return false;
	}
	Assert(!IsA(fc_node, SubLink));
	return expression_tree_walker(fc_node, fc_preprocess_aggrefs_walker,
								  (void *) fc_root);
}



/*
 * find_compatible_agg - 搜索先前初始化的每个 Agg 结构
 *
 * 搜索先前查看的聚合，以查找与此聚合兼容的聚合，使用相同的输入参数。如果找不到兼容的聚合，则返回 -1。
 *
 * 作为副作用，这也收集了与匹配输入的现有共享每个 Trans 结构的列表。如果找不到相同的 Aggref，则该列表随后将传递给 find_compatible_trans，以查看我们是否可以至少重用另一个聚合的状态值。
 */
static int fc_find_compatible_agg(PlannerInfo *fc_root, Aggref *fc_newagg,
					List **fc_same_input_transnos)
{
	ListCell   *fc_lc;
	int			fc_aggno;

	*fc_same_input_transnos = NIL;

	/* 如果 aggref 包含易失性函数调用，则我们不能重用它 */
	if (contain_volatile_functions((Node *) fc_newagg))
		return -1;

	/*
	 * 遍历已经看到的聚合列表。如果我们找到一个
	 * 现有的相同聚合调用，那么我们可以重用它。在搜索时，
	 * 我们还会收集具有相同输入参数的 Aggrefs 列表。
	 * 如果没有找到匹配的 Aggref，调用者仍然可以潜在地
	 * 重用其中一个的过渡状态。(在这个阶段我们只比较
	 * 解析树; 不同的聚合是否共享相同的过渡函数稍后会进行检查。)
	 */
	fc_aggno = -1;
	foreach(fc_lc, fc_root->agginfos)
	{
		AggInfo    *fc_agginfo = (AggInfo *) lfirst(fc_lc);
		Aggref	   *fc_existingRef;

		fc_aggno++;

		fc_existingRef = fc_agginfo->representative_aggref;

		/* 以下所有内容必须相同，否则就不匹配 */
		if (fc_newagg->inputcollid != fc_existingRef->inputcollid ||
			fc_newagg->aggtranstype != fc_existingRef->aggtranstype ||
			fc_newagg->aggstar != fc_existingRef->aggstar ||
			fc_newagg->aggvariadic != fc_existingRef->aggvariadic ||
			fc_newagg->aggkind != fc_existingRef->aggkind ||
			!equal(fc_newagg->args, fc_existingRef->args) ||
			!equal(fc_newagg->aggorder, fc_existingRef->aggorder) ||
			!equal(fc_newagg->aggdistinct, fc_existingRef->aggdistinct) ||
			!equal(fc_newagg->aggfilter, fc_existingRef->aggfilter))
			continue;

		/* 如果是相同的聚合函数，则报告精确匹配 */
		if (fc_newagg->aggfnoid == fc_existingRef->aggfnoid &&
			fc_newagg->aggtype == fc_existingRef->aggtype &&
			fc_newagg->aggcollid == fc_existingRef->aggcollid &&
			equal(fc_newagg->aggdirectargs, fc_existingRef->aggdirectargs))
		{
			list_free(*fc_same_input_transnos);
			*fc_same_input_transnos = NIL;
			return fc_aggno;
		}

		/*
		 * 不相同，但它具有相同的输入。如果最终函数
		 * 允许共享，则将其 transno 返回给调用者，以防我们可以
		 * 重用其每个转状态。(如果已经在进行共享，
		 * 我们可能会报告多个 transno。find_compatible_trans
		 * 足够便宜，不值得花费周期去避免这种情况。)
		 */
		if (fc_agginfo->shareable)
			*fc_same_input_transnos = lappend_int(*fc_same_input_transnos,
											   fc_agginfo->transno);
	}

	return -1;
}

/*
 * find_compatible_trans - 搜索先前初始化的每个 Trans 
 * 结构
 *
 * 在 transnos 列表中搜索具有相同
 * 转换函数和初始条件的每个 Trans 结构。(输入已经过
 * 验证匹配。)
 */
static int fc_find_compatible_trans(PlannerInfo *fc_root, Aggref *fc_newagg, bool fc_shareable,
					  Oid fc_aggtransfn, Oid fc_aggtranstype,
					  int fc_transtypeLen, bool fc_transtypeByVal,
					  Oid fc_aggcombinefn,
					  Oid fc_aggserialfn, Oid fc_aggdeserialfn,
					  Datum fc_initValue, bool fc_initValueIsNull,
					  List *fc_transnos)
{
	ListCell   *fc_lc;

	/* 如果此聚合无法共享过渡状态，则放弃 */
	if (!fc_shareable)
		return -1;

	foreach(fc_lc, fc_transnos)
	{
		int			fc_transno = lfirst_int(fc_lc);
		AggTransInfo *fc_pertrans = (AggTransInfo *) list_nth(fc_root->aggtransinfos, fc_transno);

		/*
		 * 如果转函数或过渡状态类型不相同，则状态无法
		 * 被共享。
		 */
		if (fc_aggtransfn != fc_pertrans->transfn_oid ||
			fc_aggtranstype != fc_pertrans->aggtranstype)
			continue;

		/*
		 * 如果存在，序列化和反序列化函数必须匹配，
		 * 因为我们无法共享将序列化或反序列化为不同格式的聚合的转状态。
		 * 请记住，如果这些在此 agg 节点中不是必需的，则它们将是 InvalidOid。
		 */
		if (fc_aggserialfn != fc_pertrans->serialfn_oid ||
			fc_aggdeserialfn != fc_pertrans->deserialfn_oid)
			continue;

		/*
		 * 组合函数也必须匹配。我们只关心部分聚合的组合
		 * 函数，但在规划早期还不知道我们是否会进行部分聚合，因此要保守。
		 */
		if (fc_aggcombinefn != fc_pertrans->combinefn_oid)
			continue;

		/*
		 * 确认初始条件也匹配。
		 */
		if (fc_initValueIsNull && fc_pertrans->initValueIsNull)
			return fc_transno;

		if (!fc_initValueIsNull && !fc_pertrans->initValueIsNull &&
			datumIsEqual(fc_initValue, fc_pertrans->initValue,
						 fc_transtypeByVal, fc_transtypeLen))
			return fc_transno;
	}
	return -1;
}

static Datum fc_GetAggInitVal(Datum fc_textInitVal, Oid fc_transtype)
{
	Oid			fc_typinput,
				fc_typioparam;
	char	   *fc_strInitVal;
	Datum		fc_initVal;

	getTypeInputInfo(fc_transtype, &fc_typinput, &fc_typioparam);
	fc_strInitVal = TextDatumGetCString(fc_textInitVal);
	fc_initVal = OidInputFunctionCall(fc_typinput, fc_strInitVal,
								   fc_typioparam, -1);
	pfree(fc_strInitVal);
	return fc_initVal;
}


/*
 * get_agg_clause_costs
 *	  处理 PlannerInfo 的 'aggtransinfos' 和 'agginfos' 列表
 *	  累积关于它们的成本信息。
 *
 * 'aggsplit' 告诉我们预期的部分聚合模式，这会影响
 * 成本估算。
 *
 * 请注意，成本是添加到 *costs 中已经存在的... 所以调用者
 * 负责最初将结构归零。
 *
 * 对于每个 AggTransInfo，我们使用
 * transfn 或 combinefn 添加聚合过渡的成本，具体取决于 'aggsplit' 值。我们还考虑任何 aggfilters 的成本以及任何
 * 过渡状态的序列化和反序列化的成本，并估算在内存中并发存储每个聚合的状态所需的总空间
 * （正如在 HashAgg 计划中所做的那样）。
 *
 * 对于 'agginfos' 列表中的每个 AggInfo，我们添加运行
 * 最终函数和直接参数（如果有）的成本。
 */
void get_agg_clause_costs(PlannerInfo *fc_root, AggSplit fc_aggsplit, AggClauseCosts *fc_costs)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_root->aggtransinfos)
	{
		AggTransInfo *fc_transinfo = (AggTransInfo *) lfirst(fc_lc);

		/*
		 * 将适当的组件函数执行成本添加到
		 * 适当的总计中。
		 */
		if (DO_AGGSPLIT_COMBINE(fc_aggsplit))
		{
			/* 对于组合之前聚合的状态进行收费 */
			add_function_cost(fc_root, fc_transinfo->combinefn_oid, NULL,
							  &fc_costs->transCost);
		}
		else
			add_function_cost(fc_root, fc_transinfo->transfn_oid, NULL,
							  &fc_costs->transCost);
		if (DO_AGGSPLIT_DESERIALIZE(fc_aggsplit) &&
			OidIsValid(fc_transinfo->deserialfn_oid))
			add_function_cost(fc_root, fc_transinfo->deserialfn_oid, NULL,
							  &fc_costs->transCost);
		if (DO_AGGSPLIT_SERIALIZE(fc_aggsplit) &&
			OidIsValid(fc_transinfo->serialfn_oid))
			add_function_cost(fc_root, fc_transinfo->serialfn_oid, NULL,
							  &fc_costs->finalCost);

		/*
		 * 这些成本仅由初始聚合节点产生，所以我们
		 * 不应在上层再次包括它们。
		 */
		if (!DO_AGGSPLIT_COMBINE(fc_aggsplit))
		{
			/* 将输入表达式的成本添加到每个输入行的成本中 */
			QualCost	fc_argcosts;

			cost_qual_eval_node(&fc_argcosts, (Node *) fc_transinfo->args, fc_root);
			fc_costs->transCost.startup += fc_argcosts.startup;
			fc_costs->transCost.per_tuple += fc_argcosts.per_tuple;

			/*
			 * 将任何过滤器的成本添加到每个输入行的成本中。
			 *
			 * XXX 理想情况下，我们应根据过滤器选择性减少输入表达式成本，
			 * 但不清楚这是否值得。
			 */
			if (fc_transinfo->aggfilter)
			{
				cost_qual_eval_node(&fc_argcosts, (Node *) fc_transinfo->aggfilter,
									fc_root);
				fc_costs->transCost.startup += fc_argcosts.startup;
				fc_costs->transCost.per_tuple += fc_argcosts.per_tuple;
			}
		}

		/*
		 * 如果过渡类型是按值传递，则不会增加
		 * 哈希表所需的大小。如果是按引用传递，
		 * 则必须加上值本身的估计大小，以及 palloc 的开销。
		 */
		if (!fc_transinfo->transtypeByVal)
		{
			int32		fc_avgwidth;

			/* 如果聚合定义提供了平均宽度，则使用该宽度 */
			if (fc_transinfo->aggtransspace > 0)
				fc_avgwidth = fc_transinfo->aggtransspace;
			else if (fc_transinfo->transfn_oid == F_ARRAY_APPEND)
			{
				/*
				 * 如果过渡函数是 array_append()，它将使用一个
				 * 扩展数组作为 transvalue，至少占用
				 * ALLOCSET_SMALL_INITSIZE，可能更多。使用这个作为
				 * 估算，因为没有更好的想法。
				 */
				fc_avgwidth = ALLOCSET_SMALL_INITSIZE;
			}
			else
			{
				fc_avgwidth = get_typavgwidth(fc_transinfo->aggtranstype, fc_transinfo->aggtranstypmod);
			}

			fc_avgwidth = MAXALIGN(fc_avgwidth);
			fc_costs->transitionSpace += fc_avgwidth + 2 * sizeof(void *);
		}
		else if (fc_transinfo->aggtranstype == INTERNALOID)
		{
			/*
			 * INTERNAL 过渡类型是一个特殊情况：尽管 INTERNAL
			 * 是按值传递，但几乎可以肯定被用作指向某个大型数据结构的指针。
			 * 聚合定义可以提供大小的估计。如果没有提供，那么我们假设
			 * ALLOCSET_DEFAULT_INITSIZE，如果数据保存在一个私有内存上下文中，
			 * 这就是一个不错的猜测，比如 array_agg()。
			 */
			if (fc_transinfo->aggtransspace > 0)
				fc_costs->transitionSpace += fc_transinfo->aggtransspace;
			else
				fc_costs->transitionSpace += ALLOCSET_DEFAULT_INITSIZE;
		}
	}

	foreach(fc_lc, fc_root->agginfos)
	{
		AggInfo    *fc_agginfo = (AggInfo *) lfirst(fc_lc);
		Aggref	   *fc_aggref = fc_agginfo->representative_aggref;

		/*
		 * 将适当的组件函数执行成本添加到
		 * 适当的总计中。
		 */
		if (!DO_AGGSPLIT_SKIPFINAL(fc_aggsplit) &&
			OidIsValid(fc_agginfo->finalfn_oid))
			add_function_cost(fc_root, fc_agginfo->finalfn_oid, NULL,
							  &fc_costs->finalCost);

		/*
		 * 如果有直接参数，将它们的评估成本视为
		 * finalfn 的成本。
		 */
		if (fc_aggref->aggdirectargs)
		{
			QualCost	fc_argcosts;

			cost_qual_eval_node(&fc_argcosts, (Node *) fc_aggref->aggdirectargs,
								fc_root);
			fc_costs->finalCost.startup += fc_argcosts.startup;
			fc_costs->finalCost.per_tuple += fc_argcosts.per_tuple;
		}
	}
}
